-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at:
--         http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

local class = require 'mc.class'
local cmn = require 'common'
local log = require 'mc.logging'
local def = require 'biz_topo.def'
local client = require 'pcie_device.client'
local c_device_service = require 'device.device_service'
local c_topo_reader = require 'biz_topo.topo_reader'
local event = require 'infrastructure.event'
local cjson = require 'cjson'
local org_freedesktop_dbus = require 'sd_bus.org_freedesktop_dbus'
local PING_TIMEOUT_MS<const> = 5000 -- 参考libmc4lua库

---@class TopoMonitor @业务Topo检测
---@field private zone_support table<string, boolean> @当前基础板支持的ZONE区域
local c_topo_monitor = class(nil, nil, true)

local CABLE_COMPONENT_NAME<const> = 'cable'
local CABLE_FRU_TYPE<const> = 40
local UBC_STATE_ERROR<const> = 1
local UBC_STATE_NORMAL<const> = 0
local CABLE_ALARM_COMPONENT_NAME<const> = 'cable'
local UBC_LINK_CRC_ERROR_EVENT_KEY_ID = 'Cable.UBCLinkCRCCheckError'
local UBC_LINK_CRC_ERROR_EVENT_CODE = '0x28000045'
local SMBIOS_CHECK_COUNT<const> = 2
local GENERAL_HARDWARE_SERVICE_NAME<const> = 'bmc.kepler.general_hardware'
local GENERAL_HARDWARE_APP_PATH<const> = '/bmc/kepler/general_hardware/MicroComponent'

-- 匹配的是白名单之外的uid，因此这里必须是全集
local uid_map <const> = {
    ['000000010402580311'] = 'BC83PRUOA',
    ['000000010402580324'] = 'BC83PRURA',
    ['000000010402580325'] = 'BC83PRURB',
    ['00000001010302023922'] = 'BC83SMMB',
    ['00000001050302023924'] = 'BC83FDCA',
    ['00000001030302023925'] = 'BC83HBBA',
    ['00000001030302023936'] = 'BC83NHBC',
    ['00000001030302023938'] = 'BC83NHBN',
    ['00000001040302023940'] = 'BC83PRUI',
    ['00000001040302023945'] = 'BC83PRUD',
    ['00000001040302023947'] = 'BC83PRUG',
    ['00000001100302023955'] = 'BC83ETHA',
    ['00000001100302023956'] = 'BC83ETHB',
    ['00000001020302024339'] = 'BC82AMDT',
    ['00000001030302024340'] = 'BC83NHBF',
    ['00000001100302025549'] = 'BC83ETHBA',
    ['00000001040302025554'] = 'BC83PRUDA',
    ['00000001020302031825'] = 'BC83AMDA',
    ['00000001050302035475'] = 'IT31FDCA',
    ['00000001040302035478'] = 'IT31PRUB',
    ['00000001020302044488'] = 'BC83AMDB',
    ['00000001050302044490'] = 'BC83FDCC',
    ['00000001010302044491'] = 'BC83SMMBD',
    ['00000001010302044492'] = 'BC83SMMBC',
    ['00000001030302044496'] = 'BC83NHBI',
    ['00000001040302044498'] = 'BC83PRUO',
    ['00000001040302044499'] = 'BC83PRUR',
    ['00000001040302044501'] = 'BC83PRUU',
    ['00000001040302044502'] = 'BC83PRUV',
    ['00000001040302044504'] = 'BC83PRUX',
    ['00000001030302046571'] = 'BC83NHBO',
    ['00000001040302046572'] = 'BC83PRVC',
    ['00000001040302046574'] = 'BC83PRVE',
    ['00000001030302023933'] = 'BC83NHBB',
    ['00000001030302046566'] = 'BC83HBBK',
    ['00000001040302046567'] = 'BC83PRUGFB',
    ['00000001040302064316'] = 'IT22R31B',
    ['00000001030302064317'] = 'IT22BN8B'
}

local function init_biz_connector_match_info(conn)
    for i, port in pairs(conn.ports) do
        port.target_unit_uid = ''
        port.target_unit_index = 0
        port.target_unit_port_id = 0
        port.target_unit_slot_silk = ''
        port.status = 0
        port.is_discovered = false
    end
end

local function is_bcu_index_valid(bcu_index, monitor_bcu_index)
    if not bcu_index or bcu_index == 0 then
        return true
    end

    if not monitor_bcu_index or monitor_bcu_index == 0 then
        return true
    end

    if bcu_index == monitor_bcu_index then
        return true
    end

    return false
end

local function get_valid_uid(uid)
    local valid_uid = ''
    for letter in string.gmatch(uid, '[%w.]') do
        valid_uid = valid_uid .. letter
    end
    return valid_uid
end

local function set_solid_config_exist_ports(monitor, raw_uid, bcu_index, target_port_id)
    local uid = get_valid_uid(raw_uid)
    local default_key_str = string.format("%s_%s", uid, 0)
    local key_str = string.format("%s_%s", uid, bcu_index)
    if monitor.solid_config[key_str] then
        monitor.solid_config[key_str].exist_ports[target_port_id] = true
    end
    if monitor.solid_config[default_key_str] then
        monitor.solid_config[default_key_str].exist_ports[target_port_id] = true
    end
end

-- 判断SMC上报的topo_info中是否有匹配业务连接器conn的resp
local function match_topo_info_resp(port, conn, topo_info)
    if topo_info[port.zone] and topo_info[port.zone][port.id] then
        -- 默认情况
        if topo_info[port.zone][port.id][0] then
            return topo_info[port.zone][port.id][0]
        end
        -- 系列化特殊场景，resp中包含了BCUIndex检测信息(conn.bcu_index=1是主板，2是从板）
        if topo_info[port.zone][port.id][conn.bcu_index] then
            return topo_info[port.zone][port.id][conn.bcu_index]
        end
    end
    return nil
end

local function set_topo_info_to_biz_connector(conn, topo_info, monitor)
    if not is_bcu_index_valid(conn.bcu_index, monitor.monitor_bcu_index) then
        return
    end

    init_biz_connector_match_info(conn)
    local resp
    for i, port in pairs(conn.ports) do
        resp = match_topo_info_resp(port, conn, topo_info)
        if resp then
            --resp的bcu_index不为0，即存在系列化特殊情况；一个topo_node提供多个BCU的检测结果
            if resp.bcu_index and resp.bcu_index ~= 0 then
                port.bcu_index = resp.bcu_index
            end
            -- 将二进制UID解析为字符串格式
            port.target_unit_uid = resp.uid
            port.target_unit_index = resp.index
            port.target_unit_port_id = resp.target_port_id
            port.is_discovered = true

            set_solid_config_exist_ports(monitor, resp.uid, resp.bcu_index, resp.target_port_id)
        end
    end
end

local function match_name_and_port_id(config, port)
    local is_name_match = false
    for i, name in pairs(config.src_ports_name) do
        if name == port.name then
            is_name_match = true
            if config.target_ports_id[i] == port.target_unit_port_id then
                return true, is_name_match
            end
        end
    end
    return false, is_name_match
end

local function is_config_matched(config, port, monitor_bcu_index)
    if not is_bcu_index_valid(config.bcu_index, monitor_bcu_index) then
        return false
    end

    if config.uid ~= string.sub(port.target_unit_uid, 1, #config.uid) then
        return false
    end

    if config.index ~= 0 and config.index ~= port.target_unit_index then
        return false
    end

    --系列化特殊场景，port.bcu_index与config.bcu_index不一致，则匹配失败
    if port.bcu_index and config.bcu_index ~= port.bcu_index then
        log:info("port bcu_index %s dismatch unit_config bcu_index %s", port.bcu_index, config.bcu_index)
        return false
    end

    return true
end

local function match_topo_info_with_config(conn, monitor)
    local status, target_unit_slot_silk, to_matched_config_list, is_port_match
    local is_match, is_name_match
    local monitor_bcu_index = monitor.monitor_bcu_index
    local unit_configs = monitor.unit_configs

    if not is_bcu_index_valid(conn.bcu_index, monitor.monitor_bcu_index) then
        return
    end

    for _, port in pairs(conn.ports) do
        if not port.is_discovered then
            goto next
        end
        status = def.TOPO_STATUS.UNIT_NOT_SUPPORT
        target_unit_slot_silk = ''
        to_matched_config_list = {}
        for _, unit_config in pairs(unit_configs) do
            for _, config in pairs(unit_config.configs) do
                 if is_config_matched(config, port, monitor_bcu_index) then
                    port.target_unit_uid = config.uid
                    config.silk_text = unit_config:get_prop('SlotSilkText')
                    table.insert(to_matched_config_list, config)
                    log:info(
                        [[[BizTopoMonitor] config matched, port(zone %s, id %s, bcu_idx %s, targetUnitIdx:%s,
                        targetUnitPort: %s), target_unit(uid %s, silk %s, bcu_idx %s, index: %s)]], 
                        port.zone, port.id, port.bcu_index, port.target_unit_index, port.target_unit_port_id, 
                        config.uid, config.silk_text, config.bcu_index, config.index)
                end
            end
        end

        is_port_match = false
        -- 多种配置，满足任一配置均不告警
        for _, to_matched_config in pairs(to_matched_config_list) do
            to_matched_config.connected_times = to_matched_config.connected_times + 1
            is_match, is_name_match = match_name_and_port_id(to_matched_config, port)
            if is_name_match then
                if status == def.TOPO_STATUS.UNIT_NOT_SUPPORT then
                    status = def.TOPO_STATUS.INCORRECT_CONNECTION
                end
                target_unit_slot_silk = to_matched_config.silk_text
            end
            if is_match then
                status = def.TOPO_STATUS.MATCHED
                to_matched_config.matched_times = to_matched_config.matched_times + 1
                is_port_match = true
            end
        end

        port.status = is_port_match and def.TOPO_STATUS.MATCHED or status
        port.target_unit_slot_silk = target_unit_slot_silk
        ::next::
    end
end

-- 拼接组件UID+槽位信息或名称，如：00000001040302023940(IEUSlot1)
local function get_unit_and_extra_info(uid, slot_silk)
    if slot_silk ~= '' then
        return uid .. '(' .. slot_silk .. ')'
    end

    for k, v in pairs(uid_map) do
        if uid:sub(1, #k) == k then
            return k .. '(' .. v .. ')'
        end
    end
    return uid
end


-- 遇到状态变化时进行打印，增加可定位性
local function print_port_status(port, conn, debounced_status, i, monitor, port_link_info)
    local ok, err = pcall(function()
        if conn:get_prop(def.PROP_PORT_STATUS[i]) ~= debounced_status or
            conn:get_prop(def.PROP_PORT_LINK_INFO[i]) ~= port_link_info then
            log:notice(
                '[BizTopoMonitor] BCU: %s, Port: name=%s, status=%s, dbd_status=%s, \
                target_unit=[uid=%s, index=%s, port_id=%s]',
                monitor.monitor_bcu_index, port.name, port.status, debounced_status, 
                port.target_unit_uid, port.target_unit_index, port.target_unit_port_id)
        else
            log:debug(
                '[BizTopoMonitor] BCU: %s, Port: name=%s, status=%s, dbd_status=%s, \
                target_unit=[uid=%s, index=%s, port_id=%s]',
                monitor.monitor_bcu_index, port.name, port.status, debounced_status, 
                port.target_unit_uid, port.target_unit_index, port.target_unit_port_id)
        end
    end)
    if not ok then
        log:error("print_port_status err=%s", err)
    end
end

local function port_stash(port)
    port.target_unit_uid_record = port.target_unit_uid
    port.status_record = port.status
    port.target_unit_index_record = port.target_unit_index
    port.target_unit_port_id_record = port.target_unit_port_id
end

local function is_port_duplicated(port0, port)
    -- 适配系列化特殊场景， 添加两个端口的bcu_inde一致的条件
    if port0.target_unit_uid == port.target_unit_uid and port0.target_unit_index == port.target_unit_index and
        port0.status == port.status and port0.bcu_index == port.bcu_index then
        return true
    end
    return false
end

local function update_port_status(conn, monitor)
    if not is_bcu_index_valid(conn.mds_obj.BCUIndex, monitor.monitor_bcu_index) then
        return
    end
    if conn:is_pcie_up_biz_connector() then
        return -- 组件上行连接器不需要更改
    end 

    local port0, port_link_info, debounced_status
    for i, port in pairs(conn.ports) do
        if i == 1 then
            goto skip
        end
        -- 当两个端口的对端组件UID、Index和TOPO检测状态一致时，初始化其中一个端口的检测状态，避免重复告警
        for i0 = 1, i - 1 do
            port0 = conn.ports[i0]
            if is_port_duplicated(port0, port) then
                port.status = def.TOPO_STATUS.INITIAL_STATE
                break
            end
        end
        ::skip::
        if def.PROP_PORT_STATUS[i] then
            debounced_status = conn.monitor_status[def.PROP_PORT_LINK_INFO[i]]:get_debounced_val(port.status)
            port_link_info = get_unit_and_extra_info(port.target_unit_uid, port.target_unit_slot_silk)
            print_port_status(port, conn, debounced_status, i, monitor, port_link_info)
            -- 设置端口状态 防抖后的值
            conn:set_prop(def.PROP_PORT_STATUS[i], debounced_status)
            -- 设置端口信息（连接的组件UID+Slot丝印）
            conn:set_prop(def.PROP_PORT_LINK_INFO[i], port_link_info)
        end
        port_stash(port)
    end
end

local function print_config(unit_config, status, bcu_index, silk_text)
    local ok, err = pcall(function()
        if unit_config:get_prop(def.PROP_PORT_STATUS[1]) ~= status then
            log:notice('[BizTopoMonitor] BCU: %s, Config: slot=%s-%s, status=%s, silk_text=%s',
                bcu_index, unit_config:get_prop('SlotType'), unit_config:get_prop('SlotNumber'), status, silk_text)
        end
    end)
    if not ok then
        log:error("print_config err=%s", err)
    end
end

local function print_biz_conn_status(conn, status, bcu_index, silk_text)
    local ok, err = pcall(function()
        if conn:get_prop(def.PROP_PORT_STATUS[1]) ~= status then
            log:notice('[BizTopoMonitor] BCU=%s, ObjectName=%s, status=%s, silk_text=%s',
                bcu_index, conn.mds_obj.ObjectName, status, silk_text)
        end
    end)
    if not ok then
        log:error("print_biz_conn_status err=%s", err)
    end
end

local function is_uid_exist(monitor, slot_type, uid, slot_number)
    -- 如果是SEU、IEU的实际槽位号不是当前UnitConfiguration的槽位，就不属于当前判断范围。
    if monitor.uid_slot_list and monitor.uid_slot_list[slot_type] and
        monitor.uid_slot_list[slot_type][slot_number] ~= uid then
        return false
    end

    local is_exu = slot_type == 'EXU'
    local is_flexio = slot_type == 'FlexIO'
    if (is_exu or is_flexio) and uid == '00000001010302023922' then
        return true
    end

    if monitor.uid_list and monitor.uid_list[uid] then
        return true
    end

    return false
end

local function get_config_status(config, connected_times, unit_config, monitor, matched_times)
    local status = def.TOPO_STATUS.UNIT_NOT_EXIT
    local slot_type = unit_config:get_prop('SlotType')
    local is_flexio = slot_type == 'FlexIO'
    local is_seu = slot_type == 'SEU'
    local is_exu = slot_type == 'EXU'
    local is_ieu = slot_type == 'IEU'

    -- 防止BMC重启场景时，未发现在位的SEU，导致的告警先恢复再上报
    if is_seu then
        status = def.TOPO_STATUS.IGNORE_VALUE
    end

    -- 每个槽位同时可能支持多个配置
    if connected_times > 0 and matched_times < #config.src_ports_name then
        status = def.TOPO_STATUS.INCOMPLETE_CONNECTION
    -- IEU场景，如果组件在位但未插UBC线缆，仍然需要告警
    elseif is_ieu and connected_times == 0 then
        status = def.TOPO_STATUS.INCOMPLETE_CONNECTION
    elseif config.matched_times == #config.src_ports_name then
        status = def.TOPO_STATUS.MATCHED
    -- SEU场景，如果bios未加载完设置为忽略值，防止子系统重启时告警消失又上报
    elseif is_seu and monitor.bios_complete_count < SMBIOS_CHECK_COUNT then
        status = def.TOPO_STATUS.IGNORE_VALUE
    -- SEU场景，没插线但发现raid卡不需要告警
    elseif is_seu and monitor.has_raid then
        status = def.TOPO_STATUS.MATCHED
    -- SEU场景，如果bios加载完毕且没发现raid卡，则认为SEU连线缺失
    elseif is_seu and monitor.bios_complete_count >= SMBIOS_CHECK_COUNT and not monitor.has_raid then
        status = def.TOPO_STATUS.INCOMPLETE_CONNECTION
    elseif (is_flexio or is_exu) and config.matched_times == 0 then
        status = def.TOPO_STATUS.UNIT_NOT_EXIT
    end

    return status
end

local function update_solid_config_status(monitor, unit_config, config_uid, config_bcu_index, silk_text, config_status)
    local slot_number = unit_config:get_prop('SlotNumber')
    local port_link_info = silk_text --组件信息是Port1LinkInfo，组件连接器信息是CSR配置的Port2LinkInfo
    local conn_port_id, status
    local key_str = string.format("%s_%s", config_uid, config_bcu_index)
    for k, conn in pairs(monitor.biz_connectors) do
        if conn:is_pcie_up_biz_connector() and conn.container_uid == config_uid and
            conn.container_slot == slot_number then
            -- 判断是否需要告警
            status = def.TOPO_STATUS.INITIAL_STATE
            conn_port_id = conn.target_port_id
            if (config_status == def.TOPO_STATUS.INCOMPLETE_CONNECTION or
                config_status == def.TOPO_STATUS.UNIT_NOT_EXIT) and
                monitor.solid_config[key_str].lack_ports[conn_port_id] then
                -- 需要告警，找到缺少哪些线缆的上行连接器
                status = def.TOPO_STATUS.INCOMPLETE_CONNECTION
            end
        
            print_biz_conn_status(conn, status, config_bcu_index, port_link_info)
            local debounced_status = conn.monitor_status[def.PROP_PORT_LINK_INFO[1]]:get_debounced_val(status)
            -- 设置端口信息（连接的组件UID+Slot丝印）
            conn:set_prop(def.PROP_PORT_LINK_INFO[1], port_link_info)
            -- 设置端口状态 防抖后的值
            conn:set_prop(def.PROP_PORT_STATUS[1], debounced_status)
        end
    end
end

local function update_config_status(unit_config, monitor)
    local status = def.TOPO_STATUS.INITIAL_STATE
    local slot_type = unit_config:get_prop('SlotType')
    local is_seu = slot_type == 'SEU'
    local is_ieu = slot_type == 'IEU'
    local silk_text = ''
    local connected_times
    local monitor_bcu_index = monitor.monitor_bcu_index
    local config_uid = ''
    local config_bcu_index = 0
    
    for _, config in pairs(unit_config.configs) do
        config_uid = config.uid
        config_bcu_index = config.bcu_index or 0
        -- 属于同一BCU的配置要配在一个unit_configuration中
        if not is_bcu_index_valid(config.bcu_index, monitor_bcu_index) then
            return
        end

        if (is_seu or is_ieu) and not is_uid_exist(monitor, slot_type, config.uid,
            unit_config:get_prop('SlotNumber')) then
            goto continue
        end

        -- 当index为0时，多卡场景无法区分连线信息中的uid具体属于哪个卡，此场景下只检查漏插，不检查插错
        -- 因此在这种场景下，连对次数就是连接次数
        connected_times = (config.index == 0 and config.matched_times or config.connected_times)
        silk_text = get_unit_and_extra_info(config.uid, unit_config:get_prop('SlotSilkText'))
        status = get_config_status(config, connected_times, unit_config, monitor, config.matched_times)
        update_solid_config_status(monitor, unit_config, config_uid, config_bcu_index, silk_text, status)
        
        if status == def.TOPO_STATUS.MATCHED then
            break
        end

        ::continue::
    end

    print_config(unit_config, status, monitor_bcu_index, silk_text)
    -- 默认使用Port1Status承载检测状态
    unit_config:set_prop(def.PROP_PORT_LINK_INFO[1], silk_text)
    unit_config:set_prop(def.PROP_PORT_STATUS[1], 
        unit_config.monitor_status[def.PROP_PORT_LINK_INFO[1]]:get_debounced_val(status))
end

local function init_config_match_info(unit_config)
    for _, config in pairs(unit_config.configs) do
        config.matched_times = 0
        config.connected_times = 0
    end
end

-- 调用callback处理集合中的每个对象
local function process(coll, callback, ...)
    local ok
    for _, item in pairs(coll) do
        ok = callback(item, ...)
        if ok == false then
            return false
        end
    end
    return true
end

local function get_latest_alarm_list(bus, bcu_index)
    local latest_alarm_list = {}
    local ok, alarm_list = pcall(event.get_latest_alarm_list, bus)
    if ok and alarm_list then
        log:debug('get_latest_alarm_list successfully.')
    else
        log:notice('get_latest_alarm_list failed, ok: %s, alarm_list: %s', ok , alarm_list)
        return latest_alarm_list
    end

    local alarm_msg_args, location, bcu_slot, ubc_name
    for _, alarm in pairs(alarm_list) do
        if not alarm.ComponentName or alarm.ComponentName ~= CABLE_ALARM_COMPONENT_NAME then
            log:debug('alarm.ComponentName not match ComponentName: %s', alarm.ComponentName)
            goto continue
        end

        if not alarm.EventCode or alarm.EventCode ~= UBC_LINK_CRC_ERROR_EVENT_CODE then
            log:debug('alarm.EventKeyId(%s) not match', alarm.EventKeyId)
            goto continue
        end

        if not alarm.MessageArgs or alarm.MessageArgs == "" then
            log:debug('alarm.MessageArgs empty')
            goto continue
        end

        ok, alarm_msg_args = pcall(cjson.decode, alarm.MessageArgs)
        if not ok then
            log:debug('alarm.MessageArgs decode failed, err: %s', alarm_msg_args)
            goto continue
        end
        location = alarm_msg_args[1]
        if not location or location == "" then
            goto continue
        end
        bcu_slot = tonumber(location:match('(%d+)'))
        if not bcu_slot or not bcu_index or bcu_slot ~= bcu_index then
            log:debug('alarm.MessageArgs local failed, location: %s, bcu_index: %s', location, bcu_index)
            goto continue
        end

        ubc_name = alarm_msg_args[#alarm_msg_args]
        latest_alarm_list[ubc_name] = alarm
        ::continue::
    end

    return latest_alarm_list
end

local function generate_event(event_key_id, msg_args, state)
    return cmn.generate_event(event_key_id, msg_args, state, CABLE_COMPONENT_NAME, CABLE_FRU_TYPE)
end

local function update_ubc_error_event(bus, ubc_error_info, bcu_index)
    local zone, ok, resp
    local location = 'BCU' .. bcu_index
    local event_key_id = UBC_LINK_CRC_ERROR_EVENT_KEY_ID
    local alarm_list = get_latest_alarm_list(bus, bcu_index)

    -- 当前不告警，但是有记录的故障，告警恢复
    for ubc, _ in pairs(alarm_list) do
        zone = string.sub(ubc, 1, 1)
        -- 标志位异常，不需要恢复
        if ubc_error_info[zone] and ubc_error_info[zone][ubc] ~= UBC_STATE_NORMAL then
            goto next
        end

        -- 消除告警
        ok, resp = pcall(generate_event, event_key_id, {location, ubc}, 'false')
        if not ok then
            log:debug('deassert event failed, error: %s', resp)
            break
        else
            log:notice('deassert event successfully, location: %s, ubc: %s', location, ubc)
        end
        ::next::
    end

    -- 当前告警，但没有记录的故障，需要告警
    for _, ubc_list in pairs(ubc_error_info) do
        for ubc, value in pairs(ubc_list) do
            if value ~= UBC_STATE_ERROR then
                goto skip
            end

            -- 已告警，跳过
            if alarm_list and alarm_list[ubc] then
                log:debug('assert event alarmed skip, ubc: %s', ubc)
                goto skip
            end

            -- 触发告警
            ok, resp = pcall(generate_event, event_key_id, {location, ubc}, 'true')
            if not ok then
                log:debug('assert event failed location: %s, ubc: %s, error: %s', location, ubc, resp)
                break
            else
                log:notice('assert event successfully, location: %s, ubc: %s', location, ubc)
            end

            ::skip::
        end
    end
end

function c_topo_monitor:clearTable(tbl)
    if not tbl or type(tbl) ~= "table" then
        return
    end
    for k, v in pairs(tbl) do
        if type(v) == "table" then
            self:clearTable(v)  -- 递归处理子表
        end
        tbl[k] = nil  -- 清空当前键
    end
    tbl = nil
end

function c_topo_monitor:ubc_monitor()
    cmn.skynet.fork_loop({ count = 0 }, function()
        cmn.skynet.sleep(100 * 60) -- 等待60s

        local ok, ubc_error, ret
        while true do
            log:debug('[BizTopoMonitor] ubc monitor start , BCU: %s', self.monitor_bcu_index)
            if ubc_error then
                pcall(self.topo_reader.deallocate_ubc_error, self.topo_reader, ubc_error)
            end
            ok, ubc_error = pcall(function()
                return self.topo_reader:read_ubc_error()
            end)
            if not ok or not ubc_error then
                log:debug('[BizTopoMonitor] read ubc error failed, ret: %s', ubc_error)
                goto next
            end
            ok, ret = pcall(update_ubc_error_event, self.bus, ubc_error, self.monitor_bcu_index)
            if not ok then
                log:debug('[BizTopoMonitor] update ubc error event failed, ret: %s', ret)
            end

            ::next::
            cmn.skynet.sleep(100 * 60) -- 轮询间隔60s
        end

    end)
end

function c_topo_monitor:check_component_health()
    local err = org_freedesktop_dbus.timeout_ping(self.bus, PING_TIMEOUT_MS,
        GENERAL_HARDWARE_SERVICE_NAME, GENERAL_HARDWARE_APP_PATH)
    if not err then
        return true
    end
    return false
end

local function up_tree(objects)
    for _, object in pairs(objects) do
        object:up_tree()
    end
end

function c_topo_monitor:main()
    self:ubc_monitor()
    local topo_info = {}
    cmn.skynet.sleep(100 * 30) -- 等待30s
    cmn.skynet.fork_loop({ count = 0 }, function()
        cmn.skynet.sleep(100 * 30) -- 等待30s
        self:init_solid_config()
        while true do
            if not self:check_component_health() then
                log:error('[BizTopoMonitor] The component general_hardware is in an abnormal status.')
                goto next
            end
            self:update_env()
            log:debug('[BizTopoMonitor] Topo monitor start , BCU: %s', self.monitor_bcu_index)
            -- 初始化匹配信息
            process(self.unit_configs, init_config_match_info)

            -- 读取TOPO发现信息并设置到业务端口上
            topo_info = self.topo_reader:read_topo_info()
            if not topo_info or not next(topo_info) then
                log:error('[BizTopoMonitor] Read topo info failed.')
                goto next
            end
            process(self.biz_connectors, set_topo_info_to_biz_connector, topo_info, self)
            self:update_solid_config()

            -- 将TOPO发现信息与组件配置信息做匹配
            process(self.biz_connectors, match_topo_info_with_config, self)

            -- 更新端口检测状态
            process(self.biz_connectors, update_port_status, self)

            -- 更新组件配置检测状态
            process(self.unit_configs, update_config_status, self)

            -- 手动上树
            up_tree(self.biz_connectors)
            up_tree(self.unit_configs)

            ::next::
            pcall(self.clearTable, self, topo_info)
            cmn.skynet.sleep(100 * 60) -- 轮询间隔60s
        end
    end)
end

-- 初始化所有configuration的最大子集，确定必插的线缆。
function c_topo_monitor:init_solid_config()
    if next(self.solid_config) then
        return
    end
    local temp_set = {}
    local key_str
    for _, unit_config in pairs(self.unit_configs) do
        for _, config in pairs(unit_config.configs) do
            key_str = string.format("%s_%s", config.uid, config.bcu_index or 0)
            if not self.solid_config[key_str] then
                self.solid_config[key_str] = {
                    exist_ports = {}, -- key是target_port_id
                    lack_ports = {}, -- key是target_port_id
                    target_ports_lists = {}, 
                    target_ports_largest_subset = {},
                    silk_text = get_unit_and_extra_info(config.uid, unit_config:get_prop('SlotSilkText'))
                }
            end
            table.insert(self.solid_config[key_str].target_ports_lists, config.target_ports_id)
        end
    end
    -- 找共同最大子集
    for k, _ in pairs(self.solid_config) do
        self.solid_config[k].target_ports_largest_subset = self.solid_config[k].target_ports_lists[1]
        for _, target_port_id_list in pairs(self.solid_config[k].target_ports_lists) do
            temp_set = {}
            for _, target_port in pairs(self.solid_config[k].target_ports_largest_subset) do
                if cmn.find(target_port_id_list, target_port) ~= nil then
                    table.insert(temp_set, target_port)
                end
            end
            self.solid_config[k].target_ports_largest_subset = temp_set
        end
    end
end

-- 更新在必插线缆里缺少的target port id
function c_topo_monitor:update_solid_config()
    for k, _ in pairs(self.solid_config) do
        for _, port_id in pairs(self.solid_config[k].target_ports_largest_subset) do
            if not self.solid_config[k].exist_ports[port_id] then
                self.solid_config[k].lack_ports[port_id] = true
            end
        end
    end
end

-- 给线缆检测增加环境变量：
-- 1. smbios状态和raid卡是否在位
-- 2. 所有通过自发现发现的UID（不在位的UID没有检查的必要）
function c_topo_monitor:update_env()
    if not self.smbios_obj then
        local smbios_list = client:GetSmBiosObjects()
        if smbios_list and next(smbios_list) then
            local _, obj = next(smbios_list)
            self.smbios_obj = obj
        end
    end

    if self.smbios_obj and self.smbios_obj.SmBiosStatus == 3 then
        -- 等待两次扫到smbios加载状态为3时，再进行硬盘背板raid卡检查，防止raid卡加载慢
        if self.bios_complete_count < SMBIOS_CHECK_COUNT then
            self.bios_complete_count = self.bios_complete_count + 1
        end
    else
        self.bios_complete_count = 0
    end

    self.has_raid = c_device_service.get_instance():contain_raid()

    local board_list = client:GetUnitObjects()
    for _, board in pairs(board_list) do
        self.uid_list[board.UID] = true
    end
    -- 区分SEU、IEU，记录每个槽位对应的uid，过滤unitconfig的线缆告警。
    self:update_uid_slot_list()
end

function c_topo_monitor:update_uid_slot_list()
    local c_slot, c_type, c_uid
    for _, pai in ipairs(self.pcie_addr_info_list) do
        c_slot, c_type = pai:get_prop('ContainerSlot'), pai:get_prop('ContainerUnitType')
        c_uid = pai:get_prop('ContainerUID')
        if c_slot and c_type and self.uid_slot_list[c_type] then
            log:debug("[BizTopoMonitor] type=%s, slot=%s, uid=%s", c_type, c_slot, c_uid)
            self.uid_slot_list[c_type][c_slot] = c_uid
        end
    end
end

function c_topo_monitor:init()
    self.topo_reader = c_topo_reader.new(self.biz_topo_node, self.biz_connectors)
    if self.biz_topo_node and self.biz_topo_node.mds_obj then
        -- 系列化特殊场景， BusinesstopoNode未配置在BCU上，Type类型为Proxy
        -- 这种情况下topo_node是非BCU的， 配置monitor_bcu_index为0
        if self.biz_topo_node.mds_obj.Type == 'Proxy' then
            self.monitor_bcu_index = 0
        else
            self.monitor_bcu_index = self.biz_topo_node.mds_obj.Slot ~= 0 and self.biz_topo_node.mds_obj.Slot or 1
        end
    else
        self.monitor_bcu_index = 1
    end
end

---@param biz_connectors BusinessConnector[]
---@param unit_configs UnitConfiguration[]
function c_topo_monitor:ctor(bus, biz_topo_node, biz_connectors, unit_configs, pcie_addr_info_list)
    self.bus = bus
    self.biz_topo_node = biz_topo_node
    self.topo_reader = false
    self.biz_connectors = biz_connectors
    self.zone_support = {}
    self.unit_configs = unit_configs
    self.pcie_addr_info_list = pcie_addr_info_list
    self.bios_complete_count = 0
    self.has_raid = false
    self.smbios_obj = false
    self.uid_list = {}
    self.monitor_bcu_index = false
    self.solid_config = {} -- 固定线缆配置，用于组件线缆少插告警
    self.uid_slot_list = {
        IEU = {},
        SEU = {}
    }
end

-- test
c_topo_monitor.init_biz_connector_match_info = init_biz_connector_match_info
c_topo_monitor.init_config_match_info = init_config_match_info
c_topo_monitor.set_topo_info_to_biz_connector = set_topo_info_to_biz_connector
c_topo_monitor.match_topo_info_with_config = match_topo_info_with_config
c_topo_monitor.update_port_status = update_port_status
c_topo_monitor.update_config_status = update_config_status
c_topo_monitor.update_solid_config_status = update_solid_config_status

return c_topo_monitor
